﻿using System;
using System.ComponentModel.Design;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.ComponentModelHost;
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.TextManager.Interop;
using IOleServiceProvider = Microsoft.VisualStudio.OLE.Interop.IServiceProvider;
using System.ComponentModel;

namespace CodeScrapWindow
{
	/// <summary>
	/// This class implements the tool window exposed by this package and hosts a user control.
	///
	/// In Visual Studio tool windows are composed of a frame (implemented by the shell) and a pane, 
	/// usually implemented by the package implementer.
	///
	/// This class derives from the ToolWindowPane class provided from the MPF in order to use its 
	/// implementation of the IVsUIElementPane interface.
	/// </summary>
	[Guid(GuidList.guidToolWindowPersistanceString)]
	public class CodeScrapToolWindow : ToolWindowPane, IOleCommandTarget
	{
		#region Variables

		private CodeScrapControl _control;

		private IVsTextView _textView;
		private IVsTextBuffer _textBuffer;
		private IWpfTextViewHost _textViewHost;

		#endregion Variables

		#region Properties

		/// <summary>
		/// Gets the content.
		/// </summary>
		/// <value>The content.</value>
		public override object Content
		{
			get
			{
				if (_control == null)
				{
					_control = new CodeScrapControl();
					_control.txtCode.Content = this.TextViewHost;
					_control.ViewModel.PropertyChanged += new PropertyChangedEventHandler(CodeScrapControlViewModel_PropertyChanged);
				}
				return _control;
			}
		}

		private IWpfTextViewHost TextViewHost
		{
			get
			{
				if (_textViewHost == null)
				{
					InitializeEditor();
					IVsUserData data = _textView as IVsUserData;
					if (data != null)
					{
						object obj;
						Guid guid = Microsoft.VisualStudio.Editor.DefGuidList.guidIWpfTextViewHost;
						int hr = data.GetData(ref guid, out obj);
						if (hr == VSConstants.S_OK && obj != null && obj is IWpfTextViewHost)
						{
							_textViewHost = obj as IWpfTextViewHost;
						}
					}
				}
				return _textViewHost;
			}
		}

		#endregion Properties

		#region Constructor

		/// <summary>
		/// Initializes a new instance of the <see cref="CodeScrapToolWindow"/> class.
		/// </summary>
		public CodeScrapToolWindow()
			: base(null)
		{
			// Set the window title from resources
			this.Caption = Resources.ToolWindowTitle;

			// Set the toolbar from the menu defined in CodeScrap.vsct
			this.ToolBar = new CommandID(GuidList.guidCodeScrapCmdSet, PkgCmdIDList.cmdidCodeScrapToolbar);

			// Set the image that will appear on the tab of the window frame when docked with another window
			// The resource ID corresponds to the one defined in resources while the index is the offset in the bitmap strip
			this.BitmapResourceID = 302;
			this.BitmapIndex = 0;
		}

		#endregion Constructor

		#region Editor Methods

		private void CodeScrapControlViewModel_PropertyChanged(object sender, PropertyChangedEventArgs e)
		{
			if (e.PropertyName == CodeScrapControlViewModel.CompilerPropertyName)
			{
				SetLanguageService();
			}
		}

		private void InitializeEditor()
		{
			IComponentModel componentModel = (IComponentModel)Microsoft.VisualStudio.Shell.Package.GetGlobalService(typeof(SComponentModel));
			ITextBufferFactoryService bufferFactory = componentModel.GetService<ITextBufferFactoryService>();
			IVsEditorAdaptersFactoryService editorAdapterFactory = componentModel.GetService<IVsEditorAdaptersFactoryService>();

			IOleServiceProvider serviceProvider = (IOleServiceProvider)GetService(typeof(IOleServiceProvider));

			// Create a new text buffer
			_textBuffer = editorAdapterFactory.CreateVsTextBufferAdapter(serviceProvider, bufferFactory.TextContentType);

			// Initialize the content in the text buffer with the default code
			string message = (_control != null) ? _control.ViewModel.Code : "";
			_textBuffer.InitializeContent(message, message.Length);

			// Set the language service in the text buffer for syntax coloring
			SetLanguageService();

			_textView = editorAdapterFactory.CreateVsTextViewAdapter(serviceProvider);
			((IVsWindowPane)_textView).SetSite(serviceProvider);

			var initView = new[] { new INITVIEW() };
			initView[0].fDragDropMove = 1;
			initView[0].fSelectionMargin = 0;
			initView[0].fWidgetMargin = 0;
			initView[0].fVirtualSpace = 0;

			_textView.Initialize((IVsTextLines)_textBuffer, IntPtr.Zero,
			  (uint)TextViewInitFlags.VIF_HSCROLL |
			  (uint)TextViewInitFlags.VIF_VSCROLL |
			  (uint)TextViewInitFlags3.VIF_NO_HWND_SUPPORT, initView);
		}

		private void SetLanguageService()
		{
			if (_textBuffer != null && _control != null && _control.ViewModel != null)
			{
				ICompilerProvider compiler = CompilerFactory.GetCompilerProvider(_control.ViewModel.Compiler);
				if (!string.IsNullOrEmpty(compiler.LanguageServiceGuid))
				{
					Guid languageServiceGuid = new Guid(compiler.LanguageServiceGuid);
					ErrorHandler.ThrowOnFailure(_textBuffer.SetLanguageServiceID(ref languageServiceGuid));
				}
				// TODO: ARGGGGH
				//else
				//{
				//    Guid languageServiceGuid = Guid.Empty;
				//    _textBuffer.SetLanguageServiceID(ref languageServiceGuid);
				//}
			}
		}

		/// <summary>
		/// Gets the code.
		/// </summary>
		/// <returns></returns>
		public string GetCode()
		{
			if (_textBuffer == null)
			{
				return null;
			}

			IVsTextLines lines = _textBuffer as IVsTextLines;

			if (lines == null)
			{
				return null;
			}

			string text;
			int endLine;
			int endIndex;
			ErrorHandler.ThrowOnFailure(lines.GetLastLineIndex(out endLine, out endIndex));
			ErrorHandler.ThrowOnFailure(lines.GetLineText(0, 0, endLine, endIndex, out text));

			return text;
		}

		public override void OnToolWindowCreated()
		{
			// Register key bindings to use in the editor
			IVsWindowFrame windowFrame = (IVsWindowFrame)Frame;
			Guid cmdUi = VSConstants.GUID_TextEditorFactory;
			windowFrame.SetGuidProperty((int)__VSFPROPID.VSFPROPID_InheritKeyBindings, ref cmdUi);
			base.OnToolWindowCreated();
		}

		protected override bool PreProcessMessage(ref Message m)
		{
			if (this.TextViewHost != null)
			{
				// Copy the Message into a MSG[] array, so we can pass it along to the active core editor's IVsWindowPane.TranslateAccelerator
				MSG[] pMsg = new MSG[1];
				pMsg[0].hwnd = m.HWnd;
				pMsg[0].message = (uint)m.Msg;
				pMsg[0].wParam = m.WParam;
				pMsg[0].lParam = m.LParam;

				IVsWindowPane vsWindowPane = (IVsWindowPane)_textView;
				return vsWindowPane.TranslateAccelerator(pMsg) == 0;
			}
			return base.PreProcessMessage(ref m);
		}

		/// <summary>
		/// Executes the specified command.
		/// </summary>
		/// <param name="pguidCmdGroup">The GUID of the command group.</param>
		/// <param name="nCmdID">The command ID.</param>
		/// <param name="nCmdexecopt">Specifies how the object should execute the command. Possible values are taken from the <see cref="T:Microsoft.VisualStudio.OLE.Interop.OLECMDEXECOPT"/> and <see cref="T:Microsoft.VisualStudio.OLE.Interop.OLECMDID_WINDOWSTATE_FLAG"/> enumerations.</param>
		/// <param name="pvaIn">The input arguments of the command.</param>
		/// <param name="pvaOut">The output arguments of the command.</param>
		/// <returns>
		/// This method returns S_OK on success. Other possible return values include the following.Return codeDescriptionOLECMDERR_E_UNKNOWNGROUPThe <paramref name="pguidCmdGroup"/> parameter is not null but does not specify a recognized command group.OLECMDERR_E_NOTSUPPORTEDThe <paramref name="nCmdID"/> parameter is not a valid command in the group identified by <paramref name="pguidCmdGroup"/>.OLECMDERR_E_DISABLEDThe command identified by <paramref name="nCmdID"/> is currently disabled and cannot be executed.OLECMDERR_E_NOHELPThe caller has asked for help on the command identified by <paramref name="nCmdID"/>, but no help is available.OLECMDERR_E_CANCELEDThe user canceled the execution of the command.
		/// </returns>
		int IOleCommandTarget.Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
		{
			int hr = (int)Microsoft.VisualStudio.OLE.Interop.Constants.OLECMDERR_E_NOTSUPPORTED;
			if (_textView != null)
			{
				IOleCommandTarget cmdTarget = (IOleCommandTarget)_textView;
				hr = cmdTarget.Exec(ref pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);
			}
			return hr;
		}

		/// <summary>
		/// Queries the object for the status of one or more commands generated by user interface events.
		/// </summary>
		/// <param name="pguidCmdGroup">The GUID of the command group.</param>
		/// <param name="cCmds">The number of commands in <paramref name="prgCmds"/>.</param>
		/// <param name="prgCmds">An array of <see cref="T:Microsoft.VisualStudio.OLE.Interop.OLECMD"/> structures that indicate the commands for which the caller needs status information. This method fills the <paramref name="cmdf"/> member of each structure with values taken from the <see cref="T:Microsoft.VisualStudio.OLE.Interop.OLECMDF"/> enumeration.</param>
		/// <param name="pCmdText">An <see cref="T:Microsoft.VisualStudio.OLE.Interop.OLECMDTEXT"/> structure in which to return name and/or status information of a single command. This parameter can be null to indicate that the caller does not need this information.</param>
		/// <returns>
		/// This method returns S_OK on success. Other possible return values include the following.Return codeDescriptionE_FAILThe operation failed.E_UNEXPECTEDAn unexpected error has occurred.E_POINTERThe <paramref name="prgCmds"/> argument is null.OLECMDERR_E_UNKNOWNGROUPThe <paramref name="pguidCmdGroup"/> parameter is not null but does not specify a recognized command group.
		/// </returns>
		int IOleCommandTarget.QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
		{
			int hr = (int)Microsoft.VisualStudio.OLE.Interop.Constants.OLECMDERR_E_NOTSUPPORTED;
			if (_textView != null)
			{
				IOleCommandTarget cmdTarget = (IOleCommandTarget)_textView;
				hr = cmdTarget.QueryStatus(ref pguidCmdGroup, cCmds, prgCmds, pCmdText);
			}
			return hr;
		}

		#endregion Editor Methods
	}
}
